Created by internet-config@share.com

IC Programming Documentation


1 Introduction

This document describes the programming interface to the Internet Configuration System. You should read this document if you maintain, or intend to write, a program that:

This document also provides enough details for you to write, maintain or extend any part of the system.

The document begins with an introduction to the system. Everyone should read this. It continues with a chapter describing how to use the system to access Internet preference. Everyone should read this too! The third chapter lists the preferences that are currently managed to the system and the forth chapter describes the application programmer interface (API) in detail. The rest of the manual provides more technical details about the system.

1.1 Goals and Design

The goal of the Internet Configuration System is to simplify the Macintosh user's experience of the Internet. The primary focus is to reduce the number of times that the user is required to enter information like their Email address.

Another important design goal was programmer simplicity. We recognised that this system would not be adopted if it was too complicated to use. Another aspect of this is that the system should be available in all major development environments.

The core of the system is a shared file, the Internet Preferences file, that contains this common preference information and an Internet Configuration application which the user runs to modify these preferences. This design was complicated by the requirement that it be capable of supporting Macs that are shared by various people. This requires that the system support search path for looking for the preference file.

One important design goal was to allow the system to be extended in the future to support new ideas such as application and user specific preferences. To achieve this we have complicated the system slightly by introducing an Internet Config Extension. This is a component that can be used to extend the system without relinking existing applications.

1.2 System Requirements

The Internet Configuration System relies on no modern system features and applications built using the system can be compatible with System 6 (and most probably System 4.1). The system will exploit the following advanced system features if they are available:

The system can be used from, and has been tested with, the following development environments:

For PowerPC development, the system has C source code for the glue to call the component. Because all Power Macintosh computers have Component Manager, we assume that Power Macintosh applications will rely on the component. There is no statically linked implementation for the PowerPC.

1.3 Parts of the System

The system is made up of four major parts. The first is the API, as defined in the interface files ICTypes.[ph], ICAPI.[ph] and ICKeys.[ph]. These provide the declarations required to use the system.

The second part is the component, held in the Internet Config Extension, that implements the functions of the API in an extensible and patchable manner. The Internet Configuration application contains a copy of this extension, and installs and registers the component when it is run. Obviously this is only possible if the Component Manager is present and the system deals with this in the following way.

The third part is an MPW object file, "ICGlue.o", that you link with your program. This glue is divided into two parts. The first part, the switch glue, checks for the presence of the Component Manager and the Internet Configuration component. If they are both present then the switch glue routes all calls directly through to the component. If they are missing, the switch glue routes all calls through to the link-implementation, which implements the API routines as a statically linked library.

The MPW object file will obviously be of no use in PowerMac development environments. There is a implementation of the C code to call the component from PowerMac environments.

Finally there is a preferences file, Internet Preferences, which is usually kept in the Preferences folder. This file holds the actual preference data and is modified by either the component or the glue depending on which system is in use at the time. This files is actually an implementation detail and its presence is only dictated by the current system. Future systems may store preferences in a completely different manner.

1.4 User Interface

The primary user interface to the Internet Preferences file is the Internet Config application. Although this application can be replaced it is important that you do not attempt to duplicate its functionality within your own application. Otherwise the focus of your application will be lost; it will become unclear whether your program is a newsreader or a preference setting program.

If you dislike the Internet Config application's user interface then you should write a small focused application that replaces it. You can choose to replace it in its entirety or just some component of it. For example, it would be quite sensible to write a small focused application that replaces the Internet Config application's File Mappings dialog with something altogether less modal.

One of the problems with providing an interface for changing preferences is that your interface will have to be modified to keep up with any modifications made to the Internet Config system. For example, the Internet Config RandomSignature extension will obsolete any user interface you provide for changing the signature.

So, in general, we recommend that you do not provide the ability to change the Internet Preferences within your application. You might want to provide an easy mechanism for launching Internet Config so that the user can change these preferences quickly. Internet Config provides an easy mechanism for doing this.

If you do provide a mechanism to change preferences that you should make sure to pay attention to the locked attribute. Any extension that is not compatible with a simple preference changing user interface will set that attribute. You can use the Internet Config ReadOnly extension to test how well your application supports locked preferences.

Finally, Internet Config 1.2 introduced API level support for allowing the user to choose between multiple different configurations. As time goes by we expect the Internet Config API to be extended to support more user interface operations.

1.5 Finding a Preference File

One of the most complicated aspects of the current Internet Configuration System is the method used to find a preferences file. Although, as stated in the previous sub-section, the preference file is an artifact of the implementation it is an important artifact and requires you to specify the search path for the preference file. The preference search path is dependent on whether your application was launched with a preference file or not.

Standard Preference Files

Under normal circumstances your program is launched without a preference file and you call Internet Config with a default search path, which it searches looking for a preference file.

The algorithm for finding the preference file is as follows:

This mechanism is designed for supporting simple applications that have preferences in their application folder or the Preferences folder.

Double-Clickable Preference Files

If your application uses double-clickable preference files to support multiple users on the single machine, you should use an alternative mechanism for finding the appropriate preference file. This algorithm entails just searching a single folder which you specify. This is designed to safely support the use of Macs in laboratory situations. Programs like Eudora and NewsWatcher, when they are launched with a preference file, should specify the folder containing the launched preferences file and Internet Config will look for the user's preference file only in that folder rather than continuing on to search the Preferences folder. Preferences can be shared between applications using aliases.

1.6 Preferences and Their Attributes

A program gets a preference, typically using the ICGetPref routine. The program specifies the preference using a key which is simply a Str255 that uniquely identifies the preference. Keys are not case sensitive and all high-bit set characters are either reserved or have a special meaning. The current list of keys is defined in Chapter 3.

The system responds to a ICGetPref request by returning a chunk of data that is the value of the preference. Some keys will return data of a fixed size; other data may be of variable length. There is no practical limit to the size of preference data.

Each preference also has a set of attributes, a long word that contains flags that provide additional information about the preference. The currently defined attributes include a locked bit and a volatile bit. The locked bit defines whether a request to modify the key's data will succeed. The volatile bit is discussed in the section on caching.

1.7 Future Extension

One of the most important designed goals of the system was that it be easily extended. This is achieved in three different ways.

Firstly the key space (remember keys are defined by Str255) is huge and more keys can be added as more common preferences are requires.

Secondly the system can be patched by replacing the component with a later, and hopefully improved, version. This component can be a bug fix component, based on the existing design, or it can be an entirely new component, one that stores preferences on a networked server for example.

Thirdly, you can write override components, that partially override an existing component using the Component Manager's capturing facility. An example of this behaviour is the Random Signature extension, which ships as part of the system.

2 Using Internet Config Services

This chapter describes how a normal Internet aware application would access, and even modify, the common Internet preferences. This chapter is important for anyone developing Internet application.

2.1 Initialising the System

This section describes how to initialise the system.

Starting Up and Shutting Down

When you start your application you should call ICStart and give it your application creator. If the system starts correctly it returns you an ICInstance. This is a private type whose only use is to supply back to the system. Normally you would store this instance in a global variable so that it can be accessed by other Internet Config related routines.

When your application shuts down it is important that you call ICStop with the ICInstance returned by ICStart. You should not call ICStop if ICStart fails.

var
  inst : ICInstance;

procedure Main;
  var
    err, junk : ICError;
begin
  err := ICStart(inst, my_creator);
  if err = noErr then begin
    err := DoMyApplication;
    junk := ICStop(inst);
  end; (* if *)
end; (* Main *)

The creator is not used in the current implementation but may be used in the future to support application specific preferences.

Once you have started the system you should then specify the preference file. You can do this in two ways, depending on how your application was started. The next two sub-sections describe these methods

Standard Preferences

If your program is started by normal means -- that is, without a preference file -- you should specify the preference file using the ICFindConfigFile routine, passing it an array of folders to search. The system searches these folders in order, from first to last, and then the Preference folder. The search algorithm was given in the previous chapter.

If you have no special search requirements then you should just call ICFindConfigFile with a count of 0 and a folders pointer of nil. The system will then just look for the preferences in the Preferences folder. The following code fragment demonstrates this.

function DoSetupSearchPaths : ICError;
begin
  DoSetupSearchPaths := ICFindConfigFile(inst, 0, nil);
end; (* DoSetupSearchPaths *)

Notice that you do not have to specify the Preferences folder because the system always searches that folder last.

Double-Clicked Preferences

If you implement double clickable preference files, you should specify your preference file using the ICFindUserConfigFile routine. This routine takes as its parameter a single directory specification. You should pass in the directory that contains the preference file that was double-clicked to launch your application. Internet Config will search that folder, and only that folder, in an attempt to find a preference file. This prevents your application from accidentally inheriting the Internet Preferences stored in the Preference folder.

The following routine demonstrates this method of specifying a preference file.

function DoSetupSearchPathsDoubleClick(pref_file : FSSpec) : ICError;
  var
    my_pref_dir : ICDirSpec;
begin
  my_pref_dir.vRefNum := pref_file.vRefNum;
  my_pref_dir.dirID := pref_file.parID;
  DoSetupSearchPathsDoubleClick := ICFindUserConfigFile(inst, 
      my_pref_dir);
end; (* DoSetupSearchPathsDoubleClick *)

One thing that you should be aware of is that Internet Config is not required to use any of the information you pass in to ICFindConfigFile or ICFindUserConfigFile to locate the preferences. For example, future versions of Internet Config may fetch preferences from a network server. However you are required to call one of these routines before reading or writing any preferences.

2.2 Basic Preference Operations

This section describes some basic preference operations, such as getting normal preferences, monitoring for preference changes and getting all preferences.

Getting Preference Information

Once you have told the system where to find a preferences file you can then proceed to get preference data. To do this you call ICGetPref or ICSetPref, as demonstrated in the following routine.

function GetEmailAddress : Str255;
  var
    err : ICError;
    size : longint;
    result : Str255;
    junk_attr : ICAttr;
begin
  size := sizeof(result);      (* max size for returned data *)
  err := ICGetPref(inst, kICEmail, junk_attr, @result, size);
  if err <> noErr then begin
    result := '';
  end; (* if *)
  GetEmailAddress := result;
end; (* GetEmailAddress *)

You can also optionally bracket these calls with ICBegin/ICEnd pairs. If you access multiple preferences then these bracketing calls can significantly speed up the job. The following routine demonstrates this.

function GetManyPreferences(var email, realname,
    mailhost : Str255) : ICError;
  var
    err, junk : ICError;
    size : longint;
    junk_attr : ICAttr;
begin
  err := ICBegin(inst, icReadOnlyPerm);
  if err = noErr then begin
    size := sizeof(email);      (* max size for returned data *)
    err := ICGetPref(inst, kICEmail, junk_attr, @email, size);
    if err = noErr then begin
      size := sizeof(email);
      err := ICGetPref(inst, kICRealName, junk_attr, @realname, size);
    end; (* if *)
    if err = noErr then begin
      size := sizeof(email);
      err := ICGetPref(inst, kICSMTPHost, junk_attr, @mailhost, size);
    end; (* if *)
    junk := ICEnd(inst);
  end; (* if *)
  GetManyPreferences := err;
end; (* GetManyPreferences *)

It is important that you call ICEnd if and only if ICBegin does not return an error. It is also important that you do not allow other applications to run (for example, by calling WaitNextEvent) between the ICBegin and ICEnd.

The API also contains routines for getting preferences directly to a handle. These routines are described in Section 4.2.

Preference Coherency

The Internet Preferences file is a shared data structure that can be accessed by multiple programs. This obviously causes consistency problems if an application reads a preference and it is then modified by some other application. Internet Config provides three mechanisms to deal with this problem. These are discussed in order of correctness.

The on demand approach requires that the application read its preferences when it actually needs them. Because the application does not hold copies of the preference, it can safely ignore the coherence problem. The primary problem with this approach is that it requires that you track down all references to specific preferences and change them to calls to Internet Config. This may or may not be easy depending on how your code base is structured.

The cache watching approach allows applications to get preferences and then look for modifications to those preference. It is centred around the preference seed, which is a number that changes whenever the preference data changes. You should get this seed and remember it immediately after reading your preference information. You should then get it at regular intervals and flush any cached preferences if the seed changes. For example, the pseudocode for your application might look like the following.

program MyCacheWatchingProgram;
begin
  start system
  get my preferences
  err := ICGetSeed(inst, seed);
  while not quit do begin
    process events
    err := ICGetSeed(inst, new_seed);
    if new_seed <> seed then begin
      reread my preferences
      seed := new_seed;
    end; (* if *)
  end; (* while *)
end; (* MyCacheWatchingProgram *)

The cache watching approach is further complicated by the volatile attribute. If you get a preference and it has this attribute set then you should not cache that preference. This feature allows certain preferences to change dynamically without affecting the seed.

The final approach is the ostrich approach. In this mechanism you just get your preferences and ignore caching issues entirely. This approach has the advantage of being the easiest to implement although it does mean that your application will not work properly at all times.

The approach you choose is up to you. We highly recommend that applications take the on demand approach but recognise that this may be difficult to do with an existing code base.

Regardless of which approach that you take you must flush any cached preferences when you launch. The seed value is not valid across reboots.

Indexing All Preferences

In some cases you might want to index through all of the preferences. You can do this using the ICCountPref and ICGetIndPref routines. The following routine shows how this is done.

procedure DumpKeys;
  var
    err : ICError;
    junk : ICError;
    ndx : longint;
    count : longint;
    key : Str255;
begin
  err := ICBegin(inst, icReadOnlyPerm);
  if err = noErr then begin
    err := ICCountPref(inst, count);
    if err = noErr then begin
      for ndx := 1 to count do begin
        err := ICGetIndPref(inst, ndx, key);
        if err = noErr then begin
          writeln(key);
        end; (* if *)
      end; (* for *)
    end; (* if *)
    junk := ICEnd(inst);
  end; (* if *)
end; (* DumpKeys *)

2.3 High-Level Operations

Internet Config provides direct support for a number of operations that are commonly performed by Macintosh Internet related applications.

Launching a URL

The ICLaunchURL routine combines Internet Config helper database with the GURL AppleEvent Suite standard to provide a simple mechanism for handing off a known URL to the user's preferred helper. The following sample code demonstrates how you can do this.

function LaunchURL(url: Str255): ICError;
  var
    start, fin: longint;
begin
  start := 0;
  fin := length(url);
  LaunchURL := ICLaunchURL(inst, '', @url[1], length(url), start, fin);
end; (* LaunchURL *)

Command Clicking

One common use for launching a URL is to provide support for the command clicking standard used by many Macintosh Internet applications. The following routine demonstrates how you might use Internet Config to implement command clicking in a TextEdit window.

procedure DoTextClick(teh : TEHandle; event : EventRecord);
  var
    err : ICError;
    sel_start, sel_end : longint;
    texth : Handle;
    s : SignedByte;
begin
  if band(event.modifiers, cmdKey) <> 0 then begin
    sel_start := teh^^.selStart;
    sel_end := teh^^.selEnd;
    texth := Handle(TEGetText(teh));
    s := HGetState(texth);
    HLock(texth);
    err := ICLaunchURL(inst, '', texth^, GetHandleSize(texth), 
        sel_start, sel_end);
    HSetState(texth, s);
    if err = noErr then begin
      TESetSelect(sel_start, sel_end, teh);
    end; (* if *)
  end else begin
    TEClick(event.where, band(event.modifiers, shiftKey) <> 0, teh);
  end; (* if *)
end; (* DoTextClick *)

There is another API routine, ICParseURL, that lets you parse a URL out of a chunk of text. This is useful if you want to get the URL without launching the corresponding helper application.

Note: Incorporating this code in your application would be foolish because ICeTEe, an extension that ships with Internet Config 1.1, implements this as a patch to _TEClick. However the code is a good outline for how you might implement command clicking in a program that doesn't use TextEdit.

Extension Mapping Incoming Files

Another operation that is commonly performed by Internet applications is to set the type of a file based on its extension. Internet Config allows you to do this very easily, using a routine such as the following.

function SetCorrectFileType(downloaded_file : FSSpec) : ICError;
  var
    err : ICError;
    entry : ICMapEntry;
    info : FInfo;
begin
  err := ICMapFilename(inst, downloaded_file.name, entry);
  if err = noErr then begin
    err := FSpGetFinfo(downloaded_file, info);
    if err = noErr then begin
      info.fdType := entry.file_type;
      info.fdCreator := entry.file_creator;
      err := FSpSetFInfo(downloaded_file, info);
    end; (* if *)
  end; (* if *)
  SetCorrectFileType := err;
end; (* SetCorrectFileType *)

ICMapFilename is one of the high level interfaces to the mappings database. There are a variety of other API routines that trade ease of use for greater efficiency and flexibility.

Extension Mapping Outgoing Files

The reverse operation to the previous example is setting the extension of a file you are going to send to a foreign file system based on its Macintosh type and creator. Again Internet Config provides the facilities for doing this is a user configurable fashion.

function SetCorrectExtension(file_to_upload : FSSpec;
    var upload_name : Str255) : ICError;
  var
    err : ICError;
    info : FInfo;
    entry : ICMapEntry;
begin
  err := FSpGetFinfo(file_to_upload, info);
  if err = noErr then begin
    err := ICMapTypeCreator (inst, info.fdType, info.fdCreator, 
        file_to_upload.name, entry);
  end; (* if *)
  if err = noErr then begin
    (* this assumes the file doesn't already have an extension, 
       the procedure is a bit more complication if it does *)
    upload_name := concat(file_to_upload.name, entry.extension);
  end; (* if *)
  SetCorrectExtension := err;
end; (* SetCorrectExtension *)

Again the routine demonstrated here is the highest level interface to this operation. There are a variety of other API routines that trade ease of use for greater efficiency and flexibility.

3 Keys

This chapter describes the keys used by Internet Config to denote preferences. It starts with a discussion of how the range of possible keys is divided and then continues on to describe each curretly support key and the type of data it returns.

3.1 Key Space

Keys must are Str255s that are case insensitive. All high bit set characters are either reserved or defined to be special.

There is currently only one special character in key strings, namely the bullet " ". This is used in two places. Firstly it allows applications to store application private preferences using the mechanism described below. Secondly it is used as a field separator for indexed entries, such as the "Helpers " entry. Indexed entries rely on the fact that the bullet character is not valid within normal keys, so all the keys beginning with "Helper " must be helper mapping entries.

If an applications wishes to store a private preference then it should prepend its key with the hexadecimal representation of its creator type and a bullet.

For example, the Internet Config application stores its window positions with the following key: 49434170 WindowPositions.

You can register more keys by mailing details to the support address for Internet Config.

3.2 Currently Defined Keys

The list of currently defined standard keys is kept in ICKeys.[ph] and you should look in that file for the most up-to-date list. Alternatively a good way of getting a rough idea of what is available is to look through the Internet Config application. The available keys cover a number of broad areas, including:

More keys are being added to IC with each release, so you should check in the interface files and on the config mailing list before creating your own keys.

3.3 String Key Types

A number of keys are based around common string types. This section describes some features of those keys.

Common Types

The following common types are used, with their standard definitions.

Scrambled PStrings

PStrings containing passwords are usually scrambled, to prevent idle snooping. The scrambling algorithm for PStrings is as following:

for i in 1 .. length(str)
  str[i] := str[i] xor ($55 + i);

Formatted PString and STR# Preferences

Both PStrings and each entry in STR# preferences can be formatted to contain all the required information about a service. Formatted strings contain three fields, each separated by colons ":". The first field contains the user displayable name for a specific service. The second field contains the machine's DNS name. The final field contains the path, which is empty for Archie servers. A typical formatted string would be "Australia:archie.au:/micros/mac/info-mac".

Host PStrings

A large number of host PStrings are used to denote default services. Internet Config itself does not interpret these strings but applications are required to. The format is as follows:

[ whitespace ] ( DNS_name | IP_number ) [ (whitespace | colon) port ] [ whitespace anything ]

Applications should endeavour to support both port numbers and also string names for ports, using the services data structure described later in the next section.

3.4 Complex Key Types

ICKeys.[ph] defines a number of data types that are the type of various preferences. The following types are defined:

Font Specification

The ICFontRecord is a fixed length record used to specify a font, size and face.

ICFontRecord = record
    size: integer;
    face: Style;
    font: Str255;
  end;

Application Specification

The ICAppSpec is a fixed length record used to specify an application.

ICAppSpec = record
    fCreator: OSType;
    name: Str63;
  end;

The program using this specification is expected to look up the location of the application in the desktop database. The name is provided to display to the user and should not affect the search.

File Specification

This is a variable length data structure used to specify a file or folder.

ICFileSpec = record
    vol_name: Str31;     (* volume that file is on *)
    vol_creation_date: longint;  (* creation date of said volume *)
    fss: FSSpec;         (* vRefNum field contains nothing of value *)
    alias: AliasRecord;  (* plus extra data, aliasSize 0 means no 
        Alias Manager present when ICFileSpec was created *)
   end;

This type is used as a 'poor man's alias'. Under System 6 the program using this data should use the first three fields to locate the file. Under System 7 the program can use the alias instead. Anyone creating these records is expected to provide the alias if they can.

Character Set Specification

This is used to specify a mapping from Macintosh ASCII to net ASCII and vice versa.

ICCharTable = record
    net_to_mac: packed array[char] of char;
    mac_to_net: packed array[char] of char;
  end;

File Type Mappings

This is used to specify extension and MIME mappings. It is discussed in detail in Section 4.4.

Services

This is used to the mapping between TCP service names and their ports. The data returned is an ICService record, which contains a count followed by an unbounded array of ICServiceEntries.

ICServices = record
    count: integer;
    services: array [1..1] of ICServiceEntry;
    (* this array is packed, so you can't index it directly *)
  end;
ICServicesPtr = ^ICServices;
ICServicesHandle = ^ICServicesPtr;

Note that each element in the array is tightly packed, which means you can't index the array directly. The format of an ICServiceEntry is defined below.

ICServiceEntry = record
    name: Str255;    (* this strings is tightly packed *)
    port: integer;   (* which means, these fields might have an *)
    flags: integer;  (* odd address *)
  end;
ICServiceEntryPtr = ^ICServiceEntry;
ICServiceEntryHandle = ICServiceEntryPtr;

The bits in the flags field are:

ICservices_tcp_bit = 0;        (* this is a TCP service *)
ICservices_tcp_mask = $00000001;
ICservices_udp_bit = 1;        (* this is a UDP service *)
ICservices_udp_mask = $00000002;

It is possible for both the UDP and TCP bits to be set, which means that the service is available via both protocols.

4 API Reference

This chapter is divided into a number of sections. The first section describes the types and constants provided by the interface. The subsequent sections describe groups of API routines, starting with the core routines.

Caution: The Internet Config API is defined originally in Pascal. This has a number of important consequences for C programmers:

As of Internet Config 1.2, all interface files are generated automatically by the evilest HyperCard stack on the planet. If you find any problems with any of the interface files, please email the support address for Internet Config.

4.1 Types and Constants

This section describes the types and constants provided by the Internet Configuration System. These are provided in ICTypes.[ph].

The following error codes can be returned by the system.

icPrefNotFoundErr = -666;  (* preference not found (duh!) *)
icPermErr = -667;          (* cannot set preference *)
icPrefDataErr = -668;      (* problem with preference data *)
icInternalErr = -669;      (* hmm, this is not good *)
icTruncatedErr = -670;    (* more data was present than was returned *)
icNoMoreWritersErr = -671; (* you cannot begin a write session 
    because someone else is already doing it *)
icNothingToOverrideErr = -672; (* no component for the override
    component to capture *)
icNoURLErr = -673;         (* no URL found *)
icConfigNotFoundErr = -674;       (* no configuration was found *)
icConfigInappropriateErr = -675;  (* incorrect manufacturer code *)

The ICAttr type is simply a longint containing flags that describe the attributes of a key and its data.

ICAttr = longint;          (* type for preference attributes *)

The ICattr_no_change constant is used when you call ICSetPref and do not want to mess around with attributes. You can supply this value and the system will not change the attribute of the preference.

ICattr_no_change = -1;     (* supply this to ICSetPref to tell it 
    not to change the attributes *)

The following bits are defined in the ICAttr type:

ICattr_locked_bit = 0;        (* bits in the preference attributes *)
ICattr_locked_mask = $00000001;  (* masks for the above *)
ICattr_volatile_bit = 1;
ICattr_ volatile _mask = $00000002;

If the locked bit is set then any attempt to set the preference will result in an error. If the volatile bit is set then you should not cache the value of this preference because it is subject to non-seed changing changes. See the section on caching preferences for more information about this issue.

The following values define the file type, creator and default name of the Internet Preferences file.

ICfiletype = 'ICAp';
ICcreator = 'ICAp';
ICdefault_file_name = 'Internet Preferences';

The ICDirSpec record is used to hold the vRefNum and dirID of a directory. An array of these is supplied to ICFindConfigFile to specify the search path. This array is defined to contain just 4 elements but is in fact arbitrarily extensible.

ICDirSpec =
  record                  (* a record that specifies a folder *)
    vRefNum: integer;
    dirID: longint;
  end;
ICDirSpecArray = array [0..3] of ICDirSpec;
ICDirSpecArrayPtr = ^ICDirSpecArray;

The ICError type is used for all error results from the system. A longint is used because we make lots of calls to Component Manager which uses longints for error codes.

ICError = longint;    (* type for error codes *)

The ICInstance type is an opaque type that is used to hold a reference to a session with the Internet Configuration System. Applications can create instances by calling ICStart, used them with any of the API routines and destroy them by calling ICStop.

ICInstance = Ptr;     (* opaque type for preference reference *)

Do not pass an ICInstance between processes. Also, be careful when passing an ICInstance between instruction set architectures, ie from PowerPC to 68K or vica versa. Because it has to support the link-in implementation, the 68K glue creates a different type of ICInstance than the PowerPC glue.

The ICPerm type is used to specify whether you wish to access the preferences for read-only or read-write.

ICPerm = (ioNoPerm, icReadOnlyPerm, icReadWritePerm);

The ICConfigRef type is used to store a permanent reference to an Internet Config configuration. The type varies in length and only the first four bytes, the manufacturer field, has a public meaning, which is described in Section 6.2.

ICConfigRef = record
  manufacturer: OSType;
  (* other private data follows *)
end;
ICConfigRefPtr = ^ICConfigRef;
ICConfigRefHandle = ^ICConfigRefPtr;

The following constants define bits in the flags field passed to ICSetConfigReference.

icNoUserInteraction_bit = 0;
icNoUserInteraction_mask = $00000001;

4.2 Core API Routines

This section documents the routines available in ICAPI.[ph] that make up the core of the Internet Config API.

Starting and Stopping Internet Config

The routines in the sub-section let you create and destroy connections, denoted by the ICInstance type, to the Internet Configuration System. Although it is usual to create one connection when your program starts and destroy it when it terminates, it is legal to create an arbitrary number of connections at any time.

function ICStart (var inst: ICInstance; creator: OSType): ICError;

You should call this routine at application initialisation time, passing it your creator type. If it returns noErr then you are all set to use the system. If it returns an error then ICInstance will be nil and you should not call ICStop.

function ICStop (inst: ICInstance): ICError;

You should call this when your application terminates, passing it the instance you got from ICStart.

Specifying a Config File

You must specify a configuration before you can operate on any preferences. The standard way of doing this is using ICFindConfigFile or ICFindUserConfigFile to configure the instance based on a file.

function ICFindConfigFile (inst: ICInstance; count: integer;
    folders: ICDirSpecArrayPtr): ICError;

This routine tells the system where to look for a configuration. You must call this before calling ICBegin or any routine that implicitly calls ICBegin. You should pass in a pointer to an array of ICDirSpecs that defines in which folders the system should look for the preference file. You should pass in count as the number of valid entries in this array. You do not have to supply the Preferences folder; the system will search there automatically if it can't find anything in the specified folders. You can set folders to nil if and only if count is 0. If the input parameters are valid the routine will always successful configure the instance, creating an empty configuration if necessary.

function ICFindUserConfigFile (inst: ICInstance;
    where: ICDirSpec): ICError;

This routine is similar to ICFindConfigFile except that it tells the system where to look for a configuration file when your application was launched using a double clickable preference file. The ICDirSpec should denote a folder that contains the double clicked preference file. Internet Config will look in that folder for Internet Config preference files, or aliases to preference files. If the input parameters are valid the routine will always successful configure the instance, creating an empty configuration if necessary.

Caution: This routine requires Internet Config 1.1 or later.

Advanced Config Specification

The routines in this section are only required in strange circumstances, typically by applications that act as the primary user interface to the Internet Config database.

function ICGeneralFindConfigFile(inst : ICInstance;
    search_prefs : Boolean; can_create : Boolean; 
    count : integer; folders : ICDirSpecArrayPtr) : ICError;

This routine acts as a more general replacement for ICFindConfigFile and ICFindUserConfigFile. It configures Internet Config based on a configuration file contained in the specified folders. Folders are prioritised based on their order in the folders array. If search_prefs is true then the Preferences folder is appended to the end of the search list. You can set folders to nil if and only if count is 0. If can_create is not set then the routine will return icConfigNotFoundErr if no appropriate configuration information is found and the instance retains its previous configuration (or lack thereof). Otherwise the instance is reconfigured using some default configuration, if necessary creating a file in the last folder searched.

Caution: This routine requires Internet Config 1.2 or later.

function ICChooseConfig(inst : ICInstance) : ICError;

This function requests the user to choose a configuration, typically using some sort of modal dialog. In the current implementation it will pop up a StandardGetFile dialog asking the user to choose a configuration file. In a network based implementation it might ask the user to specify the IP address of the configuration server and then to log in. If the routine fails with an error then the instance retains its previous configuration (or lack thereof). Likely errors include userCanceledErr and noUserInteractionAllowed.

Caution: This routine requires Internet Config 1.2 or later.

function ICChooseNewConfig(inst : ICInstance) : ICError;

This function requests the user to create a new configuration, typically using some sort of modal dialog. In the current implementation it will pop up a StandardPutFile dialog asking the user to create a configuration file. In a network based implementation it might ask the user to specify the IP address of the configuration server and then to specific their new user name. If the routine fails with an error then the instance retains its previous configuration (or lack thereof). Likely errors include userCanceledErr and noUserInteractionAllowed.

Caution: Applications that create configurations in this way should be prepared for the configuration to be empty, that is not containing any of the default preferences such as the mappings database.

Caution: This routine requires Internet Config 1.2 or later.

function ICSpecifyConfigFile (inst: ICInstance;
    config: FSSpec): ICError;

This routine is intended for use by the Internet Configuration application only. It tell the system to use a specific configuration file, bypassing the search mechanism used by ICFindConfigFile.

Saving and Restoring Configurations

The routines in this section allow you to create persistent references to a configuration, which you can transport across a network or store between restarts, and then use to configure an instance at a later date. This might be useful in a document oriented networking architecture, where you wish to save details of a specific configuration in a document in order to restore it when the document is reopened.

function ICGetConfigReference(inst : ICInstance;
    ref : ICConfigRefHandle) : ICError;

This routine returns a self-contained reference to the instance's current Internet Config configuration specification. You must create the ref handle and pass it to this routine, which resizes it appropriately and returns the configuration reference in it. It is the responsibility of the caller to both allocate and dispose of the ref handle.

Caution: The format of the config reference data is private to Internet Config, except as noted in Section 6.2. Relying on the internal details of this data will guarantee incompatibility with any override components and with future releases of Internet Config.

Caution: This routine requires Internet Config 1.2 or later.

function ICSetConfigReference(inst : ICInstance;
    ref : ICConfigRefHandle; flags : longint) : ICError;

This routine reconfigures the instance using the configuration reference contained in ref. It is responsibility of the caller to dispose of ref. Set the icNoUserInteraction_bit in flags to prevent Internet Config presenting a user interface during this operation, otherwise the implementation is free to interact with the user using a modal dialog. If the routine fails with an error then the instance retains its previous configuration (or lack thereof). Likely errors include userCanceledErr and noUserInteractionAllowed. An icConfigInappropriateErr error implies that this configuration reference was created with a different Internet Config implementation from the one where you are trying to restore it.

Caution: This routine requires Internet Config 1.2 or later.

Getting Information About an Instance

The routines in this sub-section return various pieces of information about an instance.

function ICGetConfigName(inst : ICInstance; longname : boolean;
    var name : Str255) : ICError;

This function returns a displayable string that represents the instance's current configuration. The longname boolean is a hint as to what type of string should be returned. A short string would usually be less than 32 characters, a long string may be up to 255 characters. This is only a hint which the implementation is free to ignore.

Caution: The string returned by this routine is for user display only. Relying on the format of this string will guarantee incompatibility with future releases of Internet Config.

Caution: This routine requires Internet Config 1.2 or later.

function ICGetSeed (inst: ICInstance; var seed: longint): ICError;

This routine returns the seed for the current preferences. The seed is a value that changes when any preferences are changed. You can repeatedly call this routine to determine whether any preference information you are storing is out of date. The value returned by ICGetSeed is only valid until the machine reboots, which basically means that you should not use this values across repeated launches of your application. The seed value is not valid inside a pair of ICBegin and ICEnd calls. You should sample the seed after calling ICEnd.

function ICGetPerm (inst: ICInstance; var perm:ICPerm): ICError;

This routine returns the current permissions for this instance. This routine is not very useful for applications. It was included so that overriding components could obtain this information easily.

function ICDefaultFileName (inst: ICInstance;
    var name: Str63): ICError;

This routine returns in name the name of the default file name of the Internet Preferences file. This name is used during the preference search and is also the name used to create a preference file if none is found.

This value is hardwired into the glue implementation but is set by a resource in the component version. The component calls itself to set up the default name, so a capturing component can override this action.

function ICGetComponentInstance (inst: ICInstance;
    var component_inst: univ Ptr): ICError;

This routine returns the component instance being addressed by the glue. It returns an error and nil if the glue is using its built-in routines.

Note: The type of component_inst in univ Ptr rather than ComponentInstance so that applications using ICAPI.[ph] do not need access to Components.[ph] which is not available in all development environments.

Preparing to Read and Write Preferences

The routines in this sub-section are used to prepare an instance for reading or writing preferences. These routines are not always required because some of the more common reading and writing calls perform the operation automatically. However the routines are still useful if you are making repeated read and write calls because they make the whole process much faster.

function ICBegin (inst: ICInstance; perm: ICPerm): ICError;

This routine prepares the system to read (set perm to icReadOnlyPerm) or read and write (set perm to icReadWritePerm) preferences. If this routine returns an error than you cannot access the preferences. If it returns noErr then you should proceed to access your preferences and eventually call ICEnd. You must not let any other application run, by calling WaitNextEvent or any other routine that gives time, between these calls.

Except where otherwise noted, any attempt to read, delete or write preferences without calling ICBegin will result in a paramErr.

Any attempt to reconfigure an instance while inside an ICBegin/ICEnd pair will result in a paramErr.

Caution: Calling ICBegin is likely to modify the current resource chain, normally by adding the Internet Preferences resource file at the start of the chain. This will be undone when you call the corresponding ICEnd. You should not rely on, or fail because of, this behaviour.

function ICEnd (inst: ICInstance): ICError;

This routine tells the system that you have finished accessing preference information. You must have successfully called ICBegin to call this.

Reading and Writing Preferences

The routines in this sub-section provide the ability to read, write, modify and delete Internet Config preferences.

function ICGetPref (inst: ICInstance; key: Str255;
    var attr: ICAttr; buf: Ptr; var size: longint): ICError;

This routine gets a preference's data given its key. It puts the data into a buffer that you supply. It also returns the attributes in attr. You should point buf to the beginning of your buffer and set size to its size. You can also use this routine to just get information about the preference by setting buf to nil.

You do not need to call ICBegin before calling this routine. If you do not do so then this routine will automatically called ICBegin(inst, icReadOnlyPerm) on entry and ICEnd(inst) on exit.

Key must not be the empty string. If buf is nil then no data is returned and the value of size is ignored; otherwise the value of size must not be negative and is the size of the buffer pointed to by buf. If the preference is present then the call sets attr to be the preference's attributes, size to be the preference's true size and returns noErr.

The routine may return icTruncatedErr if the buffer's size is too small to hold the data. In this case attr is valid, size contains the total size of the preference and the system has placed as many bytes of the preferences as will fit in the buffer. You may want to increase the size of the buffer and refetch the preference to recover the lost data.

On other errors the system returns attr as ICattr_no_change and size as 0. The most common error, icPrefNotFoundErr, implies that the preference associated with key is not available.

function ICSetPref (inst: ICInstance; key: Str255; attr: ICAttr;
    buf: Ptr; size: longint): ICError;

The routine sets a preference given its key, attributes and a buffer containing the preference data. You can leave the attributes unchanged by specifying an attribute of ICattr_no_change. You can leave the data unchanged by passing nil to buf. Not setting both values has no effect if the preference already exists but creates an empty preference with the default attributes otherwise.

You do not need to call ICBegin before calling this routine. If you do no do so then this routine will automatically called ICBegin(inst, icReadWritePerm) on entry and ICEnd(inst) on exit.

Key must not be the empty string. If buf is nil then the value of size is ignored; otherwise it must be the non-negative size of the data to store. If the preference is successfully modified then the routine returns noErr.

The routine returns icPermErr if the perm parameter to ICBegin was icReadOnlyPerm.

The routine also returns icPermErr if the current attr is locked, the new attr is locked and buf is not nil.

function ICGetPrefHandle (inst: ICInstance; key: Str255;
    var attr: ICAttr; var prefh: Handle): ICError;

This routine is analogous to ICGetPref except that it returns the resulting preference in a handle. You do not need to pass in a handle as prefh, the routine will allocate a new handle in the current heap.

You do not need to call ICBegin before calling this routine. If you do no do so then this routine will automatically called ICBegin(inst, icReadOnlyPerm) on entry and ICEnd(inst) on exit.

Caution: If the preference does not exist, this routine will return an empty handle. This is unlike ICGetPref and generally considered a mistake. The following routine rectifies this error.

Caution: This routine requires Internet Config 1.1 or later.

function ICFindPrefHandle(inst : ICInstance; key : Str255;
    var attr : ICAttr; prefh : Handle): ICError;

This new routine is designed to replace ICGetPrefHandle. The routine has two improvements over ICGetPrefHandle. The first is that this routine returns on error (icPrefNotFoundErr) if the preference is missing, rather than returning an empty handle as ICGetPrefHandle would. Secondly this routine requires you to pass in a prefh handle, which is standard Internet Config practice, rather than creating a new handle in the current heap. In other respects this routine operates identically to ICGetPrefHandle.

Caution: This routine requires Internet Config 1.2 or later.

function ICSetPrefHandle (inst: ICInstance; key: Str255; attr: ICAttr;
    prefh: Handle): ICError;

This routine is analogous to ICSetPref except that it takes its input as a handle. Like ICSetPref, if the handle is nil then it sets the attributes only.

You do not need to call ICBegin before calling this routine. If you do no do so then this routine will automatically called ICBegin(inst, icReadWritePerm) on entry and ICEnd(inst) on exit.

Caution: This routine requires Internet Config 1.1 or later.

function ICDeleteKey (inst: ICInstance; key: Str255): ICError;

This routine deletes a preference given its key. Key must not be the empty string. You must call ICBegin before calling this routine.

The routine returns icPrefNotFoundErr if the preference does not exist.

Enumerating All Preferences

The routines in this sub-section provide the ability to enumerate all of the preferences in the Internet Config database. You must call ICBegin before calling any of these routines.

function ICCountPref (inst: ICInstance; var count: longint): ICError;

This routine returns the total number of preferences available. If it returns an error then count will be 0.

function ICGetIndPref (inst: ICInstance; n: longint;
    var key: Str255): ICError;

This routine returns the key associated with the Nth preference. The value of n must be positive. The routine returns icPrefNotFoundErr if n is beyond the last preference.

Accessing the User Interface

We recommend that you do not provide a user interface for editing Internet Config preferences from within your application. To make it easier for users to edit Internet Config preferences, you might want to provide a mechanism to launch Internet Config from within your application. ICEditPreferences provides support for this.

function ICEditPreferences (inst: ICInstance; key: Str255): ICError;

This routine launches the Internet Config application (or brings it to the front if it's already running) and instructs it to open the preferences database associated with this instance. You must have specified a config file before calling this routine. The key parameter is a hint as to which preference you would like the application to display. If key is empty then the application doesn't display any specific preference, otherwise it displays the window that edits the associated key.

Versions of Internet Config prior to 1.2 provided no means to edit a specific entry in the Mappings database or to edit a specific helper. Prior to version 1.2 you can only display the entire Helper window by setting the key to "Helper ". As of IC 1.2 you can provide extra information in the key parameter. If you want to display the helper associated with the "ftp" URL scheme, you should set key to "Helper ftp". If you want to display the Map Entry dialog for the ".zip" extension, you should set key to "Mapping .zip".

You do not need to call ICBegin before calling this routine.

Note: This routine may have a radically effects in future implementations.

Caution: This routine requires Internet Config 1.1 or later.

4.3 URL Routines

One very common use of Internet Config is to access the list of URL helper applications. Internet Config 1.1 builds on this success by providing two routines for explicitly dealing with URLs and their helpers.

function ICParseURL (inst: ICInstance; hint: Str255;
    data: Ptr; len: longint;
    var selStart : longint; var selEnd: longint; url: Handle): ICError;

function ICLaunchURL (inst: ICInstance; hint: Str255;
    data: Ptr; len: longint;
    var selStart : longint; var selEnd: longint): ICError;

These two routines take very similar parameters and will be discussed as one. The primary difference between the routines is that ICParseURL returns the URL to the calling program, while ICLaunchURL passes the resulting URL to the appropriate helper application.

ICParseURL puts the resulting URL into the url handle. You must have created this handle before calling this routine. The routine resizes the handle appropriately. The handle should neither be locked nor purgeable.

ICLaunchURL looks up the helper for the resulting URL and launches it (or brings it to the front if it's already running) and sends it a GURL AppleEvent with the parsed URL . You can find out more about the GURL event suite from the specification referenced in the "Recommended Reading" appendix.

Both routines take a hint parameter. This parameter determines how the routines deal with 'slack' URLs. These are URLs of the form "name@host", which are interpreted in a context sensitive manner. For example in a mail program a 'slack' URL would normally be interpreted as "mailto:name@host", whereas in a news program the URL would be interpreted as a news reference. You should pass in the name of a URL scheme to the hint, for example "mailto". Alternatively you can leave the hint parameter empty.

The data and len parameters determine the location and size of the text to be parsed. The selStart and selEnd parameters determine the current selection in that text; the are interpreted in the same way as the selStart and selEnd fields of a TERec. The routine adjusts the selStart and selEnd parameters to 'select' the text that it has determined is a URL.

The exact URL parsing algorithm is as follows:

  1. if there is a selection skip to step 4
  2. expand the selection forwards to beginning of the word and backwards to the end of the word (never skip a bracket)
  3. if either end has an bracket then expand the other end to search for matching bracket
  4. strip trailing and leading whitespace
  5. strip whitespace return whitespace sequences
  6. take off angle brackets if necessary, or
  7. strip a trailing "." unless there was originally a selection
  8. remove any leading URL:
  9. extract protocol by looking forwards for ":"
  10. if no protocol and hint is not empty then prepend with hint ":"

This algorithm is subject change and you should not rely on the specific details.

You do not need to call ICBegin before calling these routines.

Caution: These routines requires Internet Config 1.1 or later.

4.4 Mappings Database Routines

One of the most important preferences in the Internet Config database is Mappings, the table of mappings between a file's extension and its type and creator. This preference is also one of the hardest to parse. In Internet Config 1.0 there was a statically linked library that you could use to access this preference. In Internet Config 1.1 this library was moved into the main API and significantly enhanced. This section describes these API extensions.

The routines are divided up into three classes, high-level, mid-level and low-level routines. You must choose which routines to call depending on what level of control you require.

Caution: None of the routines described in this section are available in Internet Config 1.0.

Mappings Data Structure

The Mapping key returns an 'array' of the ICMapEntry type.

ICMapEntry = record
    total_length: integer;   (* from beginning of record *)
    fixed_length: integer;   (* from beginning of record *)
    version: integer; (* version number of the entry, currently 0 *)
    file_type: OSType;       (* type for this entry *)
    file_creator: OSType;    (* creator for this entry *)
    post_creator: OSType;    (* creator of the post-processing *)
    flags: longint;          (* general flags *)
    extension: Str255;       (* extension for this entry *)
    creator_app_name: Str255;(* name of the creator application *)
    post_app_name: Str255;(* name of the post-processing application *)
    MIME_type: Str255;       (* MIME type for this entry *)
    entry_name: Str255;      (* user level name of the entry *)
  end;

The Mappings database is not literally an array and you cannot index it directly. It is actually a packed array of ICMapEntries, with each entry packed to remove the empty space at the end of the strings. Entries can start on an odd address and entries can contain application specific data that is not described here. We strongly recommend that you access this data structure using the routines described in this section. These routines return an unpacked ICMapEntry, which is a lot easier to deal with.

Each ICMapEntry gives the mappings from an extension to a file type and creator and vice versa. The entry also specifies an application that should be used to post-process a downloaded file of this type and a MIME type associated with this type.

The bits in the flags entry are defined in ICKeys.[ph]. They include

ICmap_binary_bit = 0;          (* file should be transferred in *)
ICmap_binary_mask = $00000001; (* binary as opposed to text mode *)

ICmap_resource_fork_bit = 1;   (* the resource fork of the file is
                                  significant *)
ICmap_resource_fork_mask = $00000002;

ICmap_data_fork_bit = 2;       (* the data fork of the file is
                                  significant *)
ICmap_data_fork_mask = $00000004;

ICmap_post_bit = 3;            (* post process using post fields *)
ICmap_post_mask = $00000008;

ICmap_not_incoming_bit = 4;    (* ignore this mapping for *)
ICmap_not_incoming_mask = $00000010; (* incoming files *)

ICmap_not_outgoing_bit = 5;    (* ignore this mapping for *)
ICmap_not_outgoing_mask = $00000020; (* outgoing files *)

The first three flags can be used to determine how to upload or download a file, specifically for protocols such as FTP. The post-processing flag is set up by the user and indicates whether applications should post-process this type after a download. The last two flags can be used to make entries asymmetric; they allow the user to use different settings depending on whether the file is being moved to or from the Macintosh. The meaning of these two flags is encoded in the operation of the high- and mid-level routines.

High-Level Routines

The high-level routines are suitable for applications that want to easily look up a file type and creator based on an extension, or vice version. These routines are significantly slower than their lower level counterparts, especially if you call them repeatedly.

Note: While you do not need to call ICBegin to call these routines, if you call them repeatedly it will be faster if you bracket those calls with an ICBegin. However, if you are calling them repeatedly it may be better to use the mid-level routines.

function ICMapFilename (inst: ICInstance;
    filename: Str255;
    var entry: ICMapEntry): ICError;

This routine takes a filename, which must not be empty, and returns the entry with the most appropriate extension. If there is no appropriate entry, the routine returns icPrefNotFoundErr. The routine works by walking the Mappings data structure from start to finish looking for the entry with the longest matching extension that doesn't have the 'not incoming' flag set. If there are two equally appropriate entries, the first one is returned.

function ICMapTypeCreator (inst: ICInstance;
    fType : OSType; fCreator: OSType; filename: Str255;
    var entry: ICMapEntry): ICError;

This routine takes a file type and creator (and optionally the file's name) and returns the most appropriate entry. If there is no appropriate entry then the routine returns icPrefNotFoundErr. The routine works by walking the Mappings data structure looking for the most appropriate entry that doesn't have the 'not outgoing' bit set . If there are two equally appropriate entries, the first one is returned. The appropriateness of an entry is determined by a match weight function:

This algorithm implies that the returned entry has a matching file type and the longest matching extension, and that entries with a matching creator are preferred over other entries.

Mid-Level Routines

The mid-level routines are exactly analogous to their high level counterparts except that they take a handle to the mappings database as a parameter instead of getting it from the database themselves. These routines are useful if you are doing many searches because they avoid the overhead of getting the mappings database each time.

function ICMapEntriesFilename (inst: ICInstance; entries: Handle;
    filename: Str255;
    var entry: ICMapEntry): ICError;

function ICMapEntriesTypeCreator (inst: ICInstance; entries: Handle;
    fType : OSType; fCreator: OSType; filename: Str255;
    var entry: ICMapEntry): ICError;

The semantics of these routines are identical to the high level routines except that they take a handle to the mappings database in entries. This parameter must not be nil.

You do not need to call ICBegin before calling these routines.

Low-Level Routines

The low-level routines give you access to the primitive operations used to implement the other mappings routines. Most of these routines either take or return a pos parameter. This is the index into the mappings database handle. You can get from one entry to the next by adding entry.total_size to the pos. You can use this to parse the entire table by starting with a pos of 0.

You do not need to call ICBegin before calling these routines.

function ICCountMapEntries (inst: ICInstance; entries: Handle;
    var count: longint): ICError;

This routine counts the number of entries in the mappings database provided. The entries parameter must be a handle to a valid mappings database.

function ICGetIndMapEntry (inst: ICInstance; entries: Handle;
    ndx: longint;
    var pos: longint; var entry: ICMapEntry): ICError;

This routine returns the entry whose index is ndx. The entries parameter must be a handle to a valid mappings database. The ndx parameter must be in the range 1 to the number of entries in the database. The routine returns the corresponding entry in the entry variable and sets pos to the position of that entry in the database.

function ICGetMapEntry (inst: ICInstance; entries: Handle;
    pos: longInt; var entry: ICMapEntry): ICError;

This routine gets a entry based on its position in the database. The entries parameter must be a handle to a valid mappings database. The pos must be between 0 and the size of the entries handle and must be the index to the first byte of the entry. The routine returns the corresponding entry in the entry variable.

function ICSetMapEntry (inst: ICInstance; entries: Handle;
    pos: longInt; var entry: ICMapEntry): ICError;

This routine sets an entry in the database. The entries parameter must be a handle to a valid mappings database. The pos must be between 0 and the size of the entries handle and must be the index to the first byte of the entry to be set. The routine sets the corresponding entry to the value specified in the entry parameter.

function ICDeleteMapEntry (inst: ICInstance; entries: Handle;
    pos: longint): ICError;

This routine deletes an entry in the database. The entries parameter must be a handle to a valid mappings database. The pos must be between 0 and the size of the entries handle and must be the index to the first byte of the entry to be deleted.

function ICAddMapEntry (inst: ICInstance; entries: Handle;
    var entry: ICMapEntry): ICError;

This routine adds an entry to the database. The entries parameter must be a handle to a valid mappings database. The routine adds the entry specified by the entry parameter to the end of the database.

Post-Processing

When moving files to the Macintosh the application can choose whether to support post-processing, that is feeding the file on to some other application which hopefully renders it into a more useful form. This is controlled by three fields in the ICMapEntry. The first is the post-processing bit in the flags. If this bit is set then the application should post-process this file (if it supports post-processing). The post-processing application is determined by the post_creator field; the post_app_name field is for display purposes only. If the post_creator is OSType(0) then no post-processing application has ever been specified by the user.

The difference between the file's creator and post-processor is subtle but important. The creator should be an application that can open and edit the file type. A post-processor is an application that can render the application in a more useful form. For example, the creator for files with the extension ".cpt" should be Compact Pro, whereas the post-processor is more likely to be StuffIt Expander.

Application Specific Data

The ICMapEntry data structure allows for an arbitrary amount of data between the end of the strings and the end of the entry as determined by the total_length field. This information is available for application specific use. The mapping routines guarantee to preserve this information when they modify the entry.

If you insert data into this area then you must conform to the following convention. Your data must start with a creator type (4 bytes) (usual your application's creator type) followed by the length of the data you've added (4 bytes), which includes the creator and length. You can then add length - 8 bytes of data after that. Your application can modify and remove data as long as it maintain the consistency of the data structure.

This protocol allows any number of applications to add data without getting in each others way.

Because the minimal length of application specific data is 8 bytes, the Internet Config application will remove any data that is less than 8 bytes long.

5 Component API Reference

This section documents the component interface to the Internet Configuration System. If you are sure that your application will be used only on systems that support the Component Manager you can talk to the component directly using this interface. About the only advantage of this is that you avoid having to link with a pile of glue that you're never going to use.

5.1 Component API

This section describes the routines available in the component API, as defined in ICCAPI.[ph]. Two of the routines are special in that they have special glue, but the vast bulk of the routines just call directly through to the component.

Constants

The following constants are defined in ICCAPI.[ph].

internetConfigurationComponentType = 'PREF';   (* the component type *)
internetConfigurationComponentSubType = 'ICAp';(* and subtype *)
internetConfigurationComponentInterfaceVersion0 = $00000000;
internetConfigurationComponentInterfaceVersion1 = $00010000;
internetConfigurationComponentInterfaceVersion2 = $00020000;
internetConfigurationComponentInterfaceVersion = internetConfigurationComponentInterfaceVersion2;

The first two constants denote the component type and subtype of the Internet Config component. The next three constants relate to the Internet Config component version. A component's version is determined by a 32 bit number. The top 16 bits are the API version, the bottom 16 bits are the implementation version. The following values correspond to releases of Internet Config:

  1. Version 1.0 returns 0 ($00000000)
  2. Version 1.1 returns 1 ($00010000)
  3. Version 1.2 returns 2 ($00020000)
  4. Version 1.3 returns 2.1 ($00020001)

Special Routines

The following routines have special glue to make them look more like their equivalent Internet Config API routines.

function ICCStart (var inst: ComponentInstance;
    creator: OSType): ICError;

If routine is glue that checks for the presence of the Component Manager and the Internet Configuration component. If it finds them it creates an instance, initialises it and returns it. If it can't find them or the initialisation fails then it returns badComponentInstance and inst is nil.

function ICCStop (inst: ComponentInstance): ICError;

This routine is glue that shuts down the instance and closes it.

Standard Routines

All of the other routines in ICCAPI.[ph] correspond exactly with their equivalents in ICAPI.[ph].

5.2 Selectors

The selectors used by the Internet Config componentare are provided in ICComponentSelectors.[ph] to aid in the development of override components. Normally an application would not need to use these values.

6 Overriding Components

Internet Config provides a powerful mechanism for programmers to override the default implementation of the preference code in a large number of applications. Anyone wielding this power should be careful that they only use it for the forces of niceness and good! Remember that any Internet Config component you write is dynamically linked into the application and the applications will expect you to obey certain rules. Some of these rules are given in this chapter but this chapter can never be exhaustive. Please use some common sense.

Caution: The override components that shipped with Internet Config 1.0 are broken in a variety of ways. Please make sure you are using the new, improved, low calorie, all-mod-cons override components provided in Internet Config 1.1 and later.

6.1 Generic Override Architecture

Writing override components is difficult, especially if you start from scratch. The Component Manager is a very cool but also very tricky to come to grips with. One thing that we discovered while building our two sample override components is that a lot of the code is common code that interfaces to the Component Manager, and that it could be abstracted out. The result of this is the Internet Config Component Generic Override Architecture [How's that for a collection of buzzwords!], which provides a framework for building override components.

Note: The Generic Override system documented here is currently only usable from Think Pascal. Building code resources varies quite dramatic between development environments and we have not had time to build the equivalent code for other systems. We are working on an Generic Override system for Metrorwerks C and Pascal and hope to ship it Real Soon Now .

Component Override Concepts

There are a number of concepts with which you must be familiar before attempting an override component. The first is the files that are provided as part of the Generic Override Architecture. These include:

The idea is that you clone ICSpecificOverride.* and modify them to build your component. ICSpecificOverride.p is a unit that has a number of entry points that you can fill out to achieve specific goals. It also declares a number of data structures that you can extend.

There are two types of global variables associated with a component. The shared globals are shared between all instances of the component and the instance globals (or just globals for short) are created for each instance of the component. Normally you would use instance globals in preference to shared globals.

There are two other terms used in the following section that you must be aware of, namely delegate and target. The delegate is reference to the component that your override component is overriding. You can call the delegate either through the delegate field of the instance globals or by returning delegateThisCallErr from one of your override routines.

The target is the component that has overridden your override component, using a ComponentSetTarget call. If, in one of your override routines, you call a component routine that is not the one you are overriding, then you should call through the target. This allows the component that is overriding your component to see that call.

Override Component Cookbook

To create a new override component, following the simple steps documented here.

  1. Make a copy of the specific override files:

  2. Change the 'thng' resource in ICSpecificOverride.rsrc to indicate your manufacturer code. Also change the 'vers' resource to reflect your override component's version and add any resources your component needs. These are the only changes required in ICSpecificOverride.rsrc; the rest of the changes are now in ICSpecificOverride.p.
  3. Change kOurComponentManufacturer to your manufacturer code.
  4. Add any shared globals to the sharedGlobals record.
  5. If you have added shared globals then initialise them in ICSOInitShared.
  6. If your shared globals need cleaning up then clean them in ICSOCleanShared.
  7. Add any instance specific globals to globalsRecord.
  8. If you have added globals then initialise them in ICSOInitGlobals.
  9. If your globals need cleaning up then clean them ICSOCleanGlobals.
  10. If you want to add a completely new routine or remove support for one of the built in routines then modify ICSOCanDo accordingly.
  11. Modify ICSOWhatToOverride to return the correct ProcPtr for each routine that you override or add.
  12. Write each routine. If you want the component to continue calling through to the captured component for this routine then have your routine return delegateThisCallErr. Alternatively if you want to modify the results of the delegate then call the delegate directly (using the delegate field in the globals record) and then don't return delegateThisCallErr.
  13. Smirk at the wonders of Component Manager.
  14. Looking inside ICGenericOverride and frown at the wonders of Component Manager.

The ICSpecificOverrride API

This sub-section describes each of the structures in ICSpecificOverride that you are allowed to modify.

const
  kOurComponentManufacturer = 'ICso';

You must modify this constant declaration to denote your component manufacturer code. If you don't have an official manufacturer code then just make something up, although having a code clash would be very bad, so try to be imaginative. Also remember to set the manufacturer code in the 'thng' resource of ICSpecificOverride.rsrc.

const
  delegateThisCallErr = $81234568;

You should most probably not modify this; it's provided in ICSpecificOverride so that you can return it from your override routines in order to delegate a call.

type
  sharedGlobals = record
    delegate: Component;
    (* add your own shared globals here *)
  end;
  sharedGlobalsPtr = ^sharedGlobals;

You can extend this record to add your own shared globals. Do not delete or modify the first field; it contains a reference to the captured component and is needed by ICGenericOverride.p.

type
  globalsRecord = record
    self: ComponentInstance;
    target: ComponentInstance;
    delegate: ComponentInstance;
    shared: sharedGlobalsPtr;
    (* add your own component specific globals here*)
  end;
  globalsPtr = ^globalsRecord;
  globalsHandle = ^globalsPtr;

You can extend this record to add your own instance globals. Do not delete or modify the existing fields; they are required by ICGenericOverride.p. You can however read the fields and use their values.

Note: Except when otherwise noted, the globals handle which is provided to each of the following API routines is not nil and is locked.

function ICSOInitShared (globals: globalsHandle): ComponentResult;

This routine is called to initialise the shared globals. If you return an error then you should make sure your part of the shared globals are 'clean'.

function ICSOCleanShared (globals: globalsHandle): ComponentResult;

This routine is called to clean the shared globals. If your shared globals contain any memory references, you should modify this routine to dispose of them.

Caution: This routine will never be called if you are running under an old version of the Component Manager. The workaround is to ignore the problem if your specific globals only bleed a small amounts of memory. If your specific globals bleed a lot of memory, or some other resource (such as open files), then you should refuse to install with older Component Managers. I think it was fixed in version 2 of the manager but you should check yourself.

function ICSOInitGlobals (globals: globalsHandle): ComponentResult;

This routine is called initialise the override specific fields of the instance globals. If it returns an error then the component instance is not created and the instance globals must be 'clean'.

function ICSOCleanGlobals (globals: globalsHandle): ComponentResult;

This routine is called to clean up the component specific globals, disposing any pointers and otherwise releasing any allocated resources.

function ICSOCanDo (globals: globalsHandle;
    selector: integer): ComponentResult;

This routine is called in response to a component CanDo request. You should set component result to:

-1, if you definitely want to say that the component can't do this

 0, if you definitely want to say that the component can do this

 1, if you want to let the delegate decide

Caution: These constants are quite different from the constants used by a standard Component Manager CanDo request.

Caution: Do not say you can do something if you require support from your delegate and it can't do that thing!

function ICSOWhatToOverride (globals: globalsHandle;
    selector: integer): ProcPtr;

Return nil if you do not want to override this selector. Return a pointer to a function with the appropriate signature if you do. The function must have the same parameters as the default implementation for this routine.

Caution: globals will not necessarily be locked and may be nil!!!

Sample Code

The current distribution provides two sample component that capture and extend the behaviour of the default Internet Config component. Although these sample programs are final code, writing override components is a complicated business and we are still unsure as to exactly how well these work. If you are interested in writing overriding component then you should be aware of their limitations. Please talk to us before you use these as the basis for your new, Way Cool overriding component.

6.2 Override Component Issues

This section documents some of the important issues for override components.

Preference Coherency

It is critical that your component maintains the following invariant: if any preferences that are not marked as volatile change then the seed must increase. If you do not maintain this invariant then you will break applications that are using the Cache Watching approach to preference consistency.

Seeds and ICBegin/ICEnd

It is critical that your application does not clash with programs using the Cache Watching approach to maintain preference consistency. This is surprisingly difficult to do! The important things to remember are that the seed in not valid within ICBegin/End pairs and that applications sample the seed after calling ICEnd. Conceivably it would be sensible to cache preference modifications inside ICBegin/ICEnd pairs and only write those preferences on the call to ICEnd. This would obviously modify the seed, which is perfectly sensible and would not cause any problems because the application shouldn't have sampled the seed yet. There is a possible problem if you want to change the preferences at ICEnd time and you want the application to notice these changes. The solution is to make the next two seeds return different values. Note that you shouldn't always return different values otherwise applications will be continuously refetching their cached preferences.

Readers and Writers

The Internet Config system uses a single writer or multiple readers approach to preference consistency. This means that either one writer or multiple readers can be accessing the preferences at any given point in time. Your component should enforce this restriction. You can determine whether a program is a reader or writer by watching the ICBegins.

At the moment, we're not entirely sure whether the current implementation actually does enforce this restriction, especially if a single application creates two instances. This is entirely besides the point!

Override Problems

The current implementation of the component supports component targeting, but the whole issue of component targeting is a tricky one. For example, it's really an implementation detail as to whether ICGetIndMapEntry calls ICGetMapEntry. Overriding base level component routines is tricky and you should be careful.

Having said that, it's important to note that Internet Config 1.0's support for targeting is incomplete. For example it calls the target component for default file name but not for the implicit ICBegin/ICEnd calls around a ICGetPref or ICSetPref and not for the ICGetPerm call. Your overriding component will have to deal with this. This situation is further complicated by the new routines added in Internet Config 1.2. We're still working on a new architecture for the component that will allow us to provide much better support for targeting.

Locking Preferences

If you override a preference in such a way that a standard user interface is no longer appropriate for changing the preference then you should make sure to mark that preference as locked. This will prevent applications that provide their own user interface for changing preferences from attempting to change the preference. A good example of this is the RandomSignature component which locks the signature so that programs do not allow the user to edit the signature using their own user interface. Obviously if you do this then you need to provide an alternative interface for editing the preference.

Configuration Reference Overrides

If you need to add information to the configuration reference data returned by your delegate you can do so by override both the GetConfigReference and SetConfigReference calls. On the former you should call through to get the delegate's configuration reference and then change the manufacturer code and insert your data (including the original manufacturer code) in the handle between the manufacturer code and the delegate's information. When you see a SetConfigReference you should check for the presence of your manufacturer code, remove your data, change the manufacturer code back to the original and then call through to your delegate.

A Internet Config Application Requirements

This chapter describes some of the technical details for writing the Internet Configuration application. Most readers will not be interested in the details contained in this chapter.

A.1 Basic Operations

The Internet Configuration application has the basic ability to create and edit Internet preferences files, which the application just views as documents. When it is launched without a specific document the application opens the Internet Preferences file in the Preferences folder. When it is launched with a document it edits that document. The documents can then be edited, closed, saved, etc as per the usual Mac interface.

The actual user interface used to edit preferences is beyond the scope of this discussion.

A.2 Component Installation

When it is launched the application first checks the version of the Internet Config Extension in the Extensions folder. If it is not present it should create it; if it out of date it should update it. This operation involves creating the file and copying a bunch of resources from the application's resource fork into the file. The application should does this regardless of whether the Component Manager is present in the hope that it will eventually become useful. Also courtesy demands that it ask the user for permission to do this beforehand.

Because it is in the Extensions folder the component will automatically be registered the next time the system starts up. However, in order to make the component available immediately the application registers the component if the Component Manager is present. This is particularly tricky if an old version of the component is already registered.

From now on the Internet Configuration application accesses the Internet Preferences file using exactly the same API as every other program. Well, more or less. There are some API calls that are designed for use specifically by the Internet Configuration application and are not recommended for use by normal applications. Also the application has inside knowledge about the file used to store preferences, which it needs in order to implement safe saves.

A.3 Setting Up Default Values

The application can now start a session using ICStart. When it opens a preference file the application makes sure that every preference that has a meaningful default value is initialised to that value.

The download folder preference is tricky one in that it is not a static value; the application must create the 'alias' to the desktop folder on the fly.

In future the application might set up the ArchiePreferred, InfoMacPreferred and UMichPreferred based on the machines reverse DNS name.

A.4 Editing Configuration Documents

The API provides one extra routine for the configuration application, namely ICSpecifyConfigFile. This allows you to open a config file in any folder directly, without having to mess around with the search path. Also the API extensions introduced with Internet Config 1.2 may allow you do perform all configuration in a totally implementation independent fashion.

B Technical Notes

This appendix contains a number of technical notes about how to use Internet Config correctly.

B.1 Text Files and the Editor Helper

If you are creating a text file and the file does not have an ICMapEntry (either because you don't have a foreign file name (such as saving an article file in NewsWatcher) or because the file's extension doesn't exist in the Mapping preference) then you should use the mapping for ".txt" to set the file's creator.

If the file has a valid foreign name and that name's extension is found in the Mapping preference then you should use the entry's type and creator.

Similarly, if you are creating a binary file without an appropriate ICMapEntry, use the mapping for ".binary".

If you wish the user to edit or read a text file, you should use the editor helper.

Due to a slight misunderstanding, old versions of the IC APIs had keys "TextCreator" and "BinaryCreator". These are not valid and should not be used.

B.2 Binary Stamp Identification with Internet Config

The following is a draft proposal. Please ask the author for a final version before writing code that relies on this.

Version 1 Dec 94

Obsoletes previous BINA proposal.

PURPOSE

Retyping files by means of extensions is often inadequate, especially when working with non-Mac systems that do not require the use of extensions. The BINA entry in the user field of the IC mappings data structure provides a "binary stamp" that can be used to identify files based on their contents rather than their extensions.

IMPLEMENTATION

This information is stored in the user data field of the IC mappings structure with the signature BINA. See the IC programmer's reference manual for information regarding this mechanism. The format of the entry is as follows:

Bytes  Contents
00-03  'BINA'
04-07  length of entire data structure plus 8 (yy)
08-09  number of signatures
0A-xx  binary signatures stored as PStrings (byte aligned)
xx-yy  (any trailing space is reserved and should be preserved)

The binary signature is the first several bytes shared by every file of the given type. If there are multiple file formats with distinct signatures (which are supported by programs such as Binary Pump) the signatures should be stored sequentially in the BINA entry.

USE

Since many file formats do not include a binary stamp, this mechanism should be used in addition to extension checking, not as a replacement for it. To match by stamp, scan the mappings list and compare each stamp to the file in question. Longer entries take precedence over shorter ones.

Binary stamps should not be applied to files marked as a text type in the main mappings record.

CONTACT

Eric Kidd

B.3 Dealing with Missing API Routines

The Internet Config API uses a small piece of glue to route calls through to the component, if it is implemented, or through to the link-in implementation otherwise. This switch glue has one non-obvious artifact of which you should be aware. The switch glue routes calls either through to the component or to the link-in implementation; there is no mix and match between the component and link-in implementation routines. If the version of the installed component is the same as, or newer than, the version of the glue you're linking to then nothing strange happens. But if the installed component is older than the one you're linking to, then the set of routines available is the set provided by the component, not the set provided by the link-in implementation.

This is because the switch glue routes all calls through to the component, even the ones that are not implemented by the component. The switch glue will not revert to the using the link-in implementation of these new routines, mainly because this was too complicated to implement within the current architecture.

You can determine the set of calls which are available using a routine like the one shown below.

function GetActualAPIVersion(inst : ICInstance) : longint;
  var
    err : ICError;
    component_instance : ComponentInstance;
begin
  err := ICGetComponentInstance(inst, component_instance);
  if err = noErr then begin
    GetActualAPIVersion  := GetComponentVersion(component_instance); 
  end else begin
    (* if we can't get the component_instance then we're working 
       with the link-in implementation which always provides all 
       services in the current API *)
    GetActualAPIVersion := 
        internetConfigurationComponentInterfaceVersion;
  end; (* if *)
end; (* GetActualAPIVersion *)

An alternative approach is to use the Component Manager's ComponentFunctionImplemented routine to query the component about a specific API routine, as shown in the next listing.

function ICAPIRoutineAvailable(inst : ICInstance; 
        what : integer) : Boolean;
  var
    err : ICError;
    component_instance : ComponentInstance;
begin
  err := ICGetComponentInstance(inst, component_instance);
  if err = noErr then begin
    ICAPIRoutineAvailable  := (ComponentFunctionImplemented(
        component_instance, what) = 1);
  end else begin
    (* if we can't get the component_instance then we're working 
       with the link-in implementation which always provides all
       services in the current API *)
    ICAPIRoutineAvailable := true;
  end; (* if *)
end; (* ICAPIRoutineAvailable *)

An alternate method of doing this is to just call the routine and check for the badComponentSelector error result.

There are a number of approaches to dealing with missing API routines, including:

Of course the approach that you take is up to you.

B.4 Determining If IC Is Installed

We're often asked how you can tell if Internet Config is installed. People want to know this so that they can disable user interface elements that are pertinent to IC. This question is not as easy as it sounds because the IC glue works even if the IC extension is not installed, so none of the routines return errors.

There are two approaches I recommend. The first is that you can call ICGetComponentInstance and test the returned instance. If it is nil, IC is operating using the link-in implementation and the IC extension isn't installed.

The alternative is to simply use the direct component API, that is the routines in the ICCAPI.[ph] file. These routines are exact equivalents of the routines in ICAPI.[ph] except that they rely on the component implementation and don't use the link-in implementation.

C Recommended Reading

Internet Config

Components

URLs and the Get URL Suite

D Credits

If you find a bug in Internet Config then please forward details to the official support address for Internet Config. Please read the Internet Config FAQ before sending mesages to this address. If you want to discuss Internet Config in general then I suggest you host that discussion on the comp.sys.mac.comm newsgroup.

If you want to discuss issues related to programming IC, please hold the discussion on the Internet Config programmers list. Instructions for joining this list are given in the Internet Config FAQ.

The Internet Configuration System was written by Quinn "The Eskimo" and Peter N Lewis over a period of way too many late nights and weekends. Certain important chunks of code were contributed by Marcus Jager and Stuart Cheshire. Craig Richmond provided a lot of help sorting out the default MIME mappings. Much of the extension to type mapping information was gleaned from Robin D H Walker's Extension-to-Type mappings file. Eric Kidd maintains the Internet Config web site and also gave invaluable insight into the problems of override components.

We would like to thank all of those on the Internet Config mailing list and all of the developers who have adopted the system.

The entire Internet Config system is public domain and can be redistributed without restriction.

The latest version of all the component of Internet Config can be FTPed from the home sites in Australia and the USA.


This document is Public Domain (really, we mean it!). No Rights Reserved

Comments: internet-config@share.com